Skip to content
This repository has been archived by the owner on Jan 6, 2025. It is now read-only.

Cli to rpc #126

Merged
merged 114 commits into from
Jun 12, 2024
Merged

Cli to rpc #126

merged 114 commits into from
Jun 12, 2024

Conversation

binarybaron
Copy link
Collaborator

No description provided.

@binarybaron
Copy link
Collaborator Author

@CodiumAI-Agent /review

@CodiumAI-Agent
Copy link

PR Analysis

  • 🎯 Main theme: Migrating from CLI to RPC
  • 📝 PR summary: This PR is about migrating the application from using a CLI process to using the RPC API. This includes changes in the main.ts file, the rpc.ts file, and the providersSlice.ts file. The PR also includes the removal of several files that were related to the CLI process.
  • 📌 Type of PR: Refactoring
  • 🧪 Relevant tests added: No
  • 🔒 Security concerns: No security concerns found

PR Feedback

  • 💡 General suggestions: The PR is well-structured and the changes are well-documented. The code is clean and follows good practices. However, it would be beneficial to add tests for the new RPC methods to ensure they work as expected. Also, it would be helpful to add error handling for the RPC calls to ensure the application can recover gracefully from any errors.

  • 🤖 Code feedback:

    • relevant file: src/main/cli/rpc.ts
      suggestion: Consider adding error handling for the RPC calls. This can be done by wrapping the RPC calls in a try-catch block and handling any errors that may occur. This will ensure that the application can recover gracefully from any errors and provide a better user experience. [important]
      relevant line: "+const rpcClient = jayson.Client.http({"

    • relevant file: src/main/cli/rpc.ts
      suggestion: It would be beneficial to add tests for the new RPC methods to ensure they work as expected. This will help catch any bugs or issues early in the development process and ensure the quality of the code. [important]
      relevant line: "+export async function makeRpcRequest("

    • relevant file: src/main/main.ts
      suggestion: Consider refactoring the code to avoid the use of setInterval. Using setInterval can lead to issues if the function it calls takes longer to execute than the interval time. Instead, consider using recursive setTimeout or async/await with a delay to ensure the previous function call has completed before the next one is made. [medium]
      relevant line: "+setInterval(() => {"

How to use

Tag me in a comment '@CodiumAI-Agent' and add one of the following commands:
/review [-i]: Request a review of your Pull Request. For an incremental review, which only considers changes since the last review, include the '-i' option.
/describe: Modify the PR title and description based on the contents of the PR.
/improve [--extended]: Suggest improvements to the code in the PR. Extended mode employs several calls, and provides a more thorough feedback.
/ask <QUESTION>: Pose a question about the PR.
/update_changelog: Update the changelog based on the PR's contents.

To edit any configuration parameter from configuration.toml, add --config_path=new_value
For example: /review --pr_reviewer.extra_instructions="focus on the file: ..."
To list the possible configuration parameters, use the /config command.

@binarybaron
Copy link
Collaborator Author

@CodiumAI-Agent /improve --extended

Comment on lines 135 to 154

cli.on('exit', async (code, signal) => {
logger.info({ subCommand, code, signal }, `CLI excited`);

onExit(code, signal);
resolveRunning();
cli = null;

try {
await readFromDatabaseAndUpdateState();
} finally {
onExit(code, signal);
resolveRunning();
}
});

[cli.stderr, cli.stdout].forEach((stream) => {
stream.setEncoding('utf8');
stream.on('data', (data: string) => {
onStdOut(data);

const logs = getLinesOfString(data)
.map((logText) => {
logger.debug(
{ subCommand, logText: logText.trim() },
'Received stdout from cli process'
);

try {
return JSON.parse(logText);
} catch (err) {
logger.debug(
{
subCommand,
logText,
err,
},
'Failed to parse CLI log'
);
}
return null;
})
.filter(isCliLog);
logger.debug({ subCommand, data }, `CLI stdout`);
const logs = getLogsFromRawFileString(data);

onLog(logs);

readFromDatabaseAndUpdateState();
});
});
} catch (e) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider handling the error in the catch block at line 154. Currently, the error is caught but not handled, which could lead to unexpected behavior.

Suggested change
cli.on('exit', async (code, signal) => {
logger.info({ subCommand, code, signal }, `CLI excited`);
onExit(code, signal);
resolveRunning();
cli = null;
try {
await readFromDatabaseAndUpdateState();
} finally {
onExit(code, signal);
resolveRunning();
}
});
[cli.stderr, cli.stdout].forEach((stream) => {
stream.setEncoding('utf8');
stream.on('data', (data: string) => {
onStdOut(data);
const logs = getLinesOfString(data)
.map((logText) => {
logger.debug(
{ subCommand, logText: logText.trim() },
'Received stdout from cli process'
);
try {
return JSON.parse(logText);
} catch (err) {
logger.debug(
{
subCommand,
logText,
err,
},
'Failed to parse CLI log'
);
}
return null;
})
.filter(isCliLog);
logger.debug({ subCommand, data }, `CLI stdout`);
const logs = getLogsFromRawFileString(data);
onLog(logs);
readFromDatabaseAndUpdateState();
});
});
} catch (e) {
try {
...
} catch (e) {
logger.error(`Error occurred: ${e.message}`);
}

Comment on lines 166 to 185
export async function startRPC() {
await spawnSubcommand(
'start-daemon',
{
'server-address': `${RPC_BIND_HOST}:${RPC_BIND_PORT}`,
},
(logs) => {
store.dispatch(rpcAddLogs(logs));
RPC_LOG_EVENT_EMITTER.emit(logs);
},
(exitCode, exitSignal) => {
store.dispatch(rpcProcessExited({ exitCode, exitSignal }));
logger.error('RPC server has stopped');
},
(text) => {
store.dispatch(rpcAppendStdOut(text));
}
);
store.dispatch(rpcInitiate());
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider adding error handling for the async function 'startRPC' at line 166. Currently, if an error occurs during the execution of 'spawnSubcommand', it will not be caught and could lead to unexpected behavior.

Suggested change
export async function startRPC() {
await spawnSubcommand(
'start-daemon',
{
'server-address': `${RPC_BIND_HOST}:${RPC_BIND_PORT}`,
},
(logs) => {
store.dispatch(rpcAddLogs(logs));
RPC_LOG_EVENT_EMITTER.emit(logs);
},
(exitCode, exitSignal) => {
store.dispatch(rpcProcessExited({ exitCode, exitSignal }));
logger.error('RPC server has stopped');
},
(text) => {
store.dispatch(rpcAppendStdOut(text));
}
);
store.dispatch(rpcInitiate());
}
export async function startRPC() {
try {
await spawnSubcommand(...);
...
} catch (e) {
logger.error(`Error occurred: ${e.message}`);
}
}

Comment on lines 128 to 136
export default async function getSavedLogsOfSwapId(
swapId: string
): Promise<CliLog[]> {
const logsFile = await getCliLogFile();
const fileData = await getFileData(logsFile);
const allLogs = getLogsFromRawFileString(fileData);

return allLogs.filter((log) => getCliLogSpanSwapId(log) === swapId);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider adding error handling for the async function 'getSavedLogsOfSwapId' at line 128. Currently, if an error occurs during the execution of 'getCliLogFile' or 'getFileData', it will not be caught and could lead to unexpected behavior.

Suggested change
export default async function getSavedLogsOfSwapId(
swapId: string
): Promise<CliLog[]> {
const logsFile = await getCliLogFile();
const fileData = await getFileData(logsFile);
const allLogs = getLogsFromRawFileString(fileData);
return allLogs.filter((log) => getCliLogSpanSwapId(log) === swapId);
}
export default async function getSavedLogsOfSwapId(swapId: string): Promise<CliLog[]> {
try {
const logsFile = await getCliLogFile();
const fileData = await getFileData(logsFile);
...
} catch (e) {
logger.error(`Error occurred: ${e.message}`);
}
}

Comment on lines 179 to 185
export async function checkBitcoinBalance() {
const response = await makeRpcRequest<BalanceBitcoinResponse>(
RpcMethod.GET_BTC_BALANCE,
{}
);
store.dispatch(rpcSetBalance(response.balance));
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider adding error handling for the async function 'checkBitcoinBalance' at line 179. Currently, if an error occurs during the execution of 'makeRpcRequest', it will not be caught and could lead to unexpected behavior.

Suggested change
export async function checkBitcoinBalance() {
const response = await makeRpcRequest<BalanceBitcoinResponse>(
RpcMethod.GET_BTC_BALANCE,
{}
);
store.dispatch(rpcSetBalance(response.balance));
}
export async function checkBitcoinBalance() {
try {
const response = await makeRpcRequest<BalanceBitcoinResponse>(RpcMethod.GET_BTC_BALANCE, {});
...
} catch (e) {
logger.error(`Error occurred: ${e.message}`);
}
}

Comment on lines 187 to 196
export async function withdrawAllBitcoin(address: string) {
store.dispatch(rpcResetWithdrawTxId());
const response = await makeRpcRequest<WithdrawBitcoinResponse>(
RpcMethod.WITHDRAW_BTC,
{
address,
}
);
store.dispatch(rpcSetWithdrawTxId(response.txid));
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider adding error handling for the async function 'withdrawAllBitcoin' at line 187. Currently, if an error occurs during the execution of 'makeRpcRequest', it will not be caught and could lead to unexpected behavior.

Suggested change
export async function withdrawAllBitcoin(address: string) {
store.dispatch(rpcResetWithdrawTxId());
const response = await makeRpcRequest<WithdrawBitcoinResponse>(
RpcMethod.WITHDRAW_BTC,
{
address,
}
);
store.dispatch(rpcSetWithdrawTxId(response.txid));
}
export async function withdrawAllBitcoin(address: string) {
try {
store.dispatch(rpcResetWithdrawTxId());
const response = await makeRpcRequest<WithdrawBitcoinResponse>(RpcMethod.WITHDRAW_BTC, {address});
...
} catch (e) {
logger.error(`Error occurred: ${e.message}`);
}
}

Comment on lines +198 to +202
export async function getSwapInfo(swapId: string) {
return makeRpcRequest<GetSwapInfoResponse>(RpcMethod.GET_SWAP_INFO, {
swap_id: swapId,
});
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider adding error handling for the async function 'getSwapInfo' at line 198. Currently, if an error occurs during the execution of 'makeRpcRequest', it will not be caught and could lead to unexpected behavior.

Suggested change
export async function getSwapInfo(swapId: string) {
return makeRpcRequest<GetSwapInfoResponse>(RpcMethod.GET_SWAP_INFO, {
swap_id: swapId,
});
}
export async function getSwapInfo(swapId: string) {
try {
return makeRpcRequest<GetSwapInfoResponse>(RpcMethod.GET_SWAP_INFO, {swap_id: swapId});
} catch (e) {
logger.error(`Error occurred: ${e.message}`);
}
}

Comment on lines 215 to 245
export async function buyXmr(
redeemAddress: string,
refundAddress: string,
provider: Provider
) {
const { swapId } = await makeRpcRequest<BuyXmrResponse>(
RpcMethod.BUY_XMR,
{
bitcoin_change_address: refundAddress,
monero_receive_address: redeemAddress,
seller: providerToConcatenatedMultiAddr(provider),
},
(logs) => {
store.dispatch(
swapAddLog({
logs,
isFromRestore: false,
})
);
},
true
);

store.dispatch(
swapInitiate({
provider,
spawnType: SwapSpawnType.INIT,
swapId,
})
);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider adding error handling for the async function 'buyXmr' at line 215. Currently, if an error occurs during the execution of 'makeRpcRequest', it will not be caught and could lead to unexpected behavior.

Suggested change
export async function buyXmr(
redeemAddress: string,
refundAddress: string,
provider: Provider
) {
const { swapId } = await makeRpcRequest<BuyXmrResponse>(
RpcMethod.BUY_XMR,
{
bitcoin_change_address: refundAddress,
monero_receive_address: redeemAddress,
seller: providerToConcatenatedMultiAddr(provider),
},
(logs) => {
store.dispatch(
swapAddLog({
logs,
isFromRestore: false,
})
);
},
true
);
store.dispatch(
swapInitiate({
provider,
spawnType: SwapSpawnType.INIT,
swapId,
})
);
}
export async function buyXmr(redeemAddress: string, refundAddress: string, provider: Provider) {
try {
const { swapId } = await makeRpcRequest<BuyXmrResponse>(RpcMethod.BUY_XMR, {...});
...
} catch (e) {
logger.error(`Error occurred: ${e.message}`);
}
}

Comment on lines 247 to 281
export async function cancelRefundSwap(swapId: string) {
const swapInfo = await getSwapInfo(swapId);
const previousLogs = await getSavedLogsOfSwapId(swapId);

store.dispatch(
swapInitiate({
provider: providerFromGetSellerResponse(swapInfo.seller),
spawnType: SwapSpawnType.CANCEL_REFUND,
swapId,
})
);

store.dispatch(
swapAddLog({
logs: previousLogs,
isFromRestore: true,
})
);

await makeRpcRequest(
RpcMethod.CANCEL_REFUND_SWAP,
{
swap_id: swapId,
},
(logs) => {
store.dispatch(
swapAddLog({
logs,
isFromRestore: false,
})
);
},
true
);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider adding error handling for the async function 'cancelRefundSwap' at line 247. Currently, if an error occurs during the execution of 'getSwapInfo', 'getSavedLogsOfSwapId' or 'makeRpcRequest', it will not be caught and could lead to unexpected behavior.

Suggested change
export async function cancelRefundSwap(swapId: string) {
const swapInfo = await getSwapInfo(swapId);
const previousLogs = await getSavedLogsOfSwapId(swapId);
store.dispatch(
swapInitiate({
provider: providerFromGetSellerResponse(swapInfo.seller),
spawnType: SwapSpawnType.CANCEL_REFUND,
swapId,
})
);
store.dispatch(
swapAddLog({
logs: previousLogs,
isFromRestore: true,
})
);
await makeRpcRequest(
RpcMethod.CANCEL_REFUND_SWAP,
{
swap_id: swapId,
},
(logs) => {
store.dispatch(
swapAddLog({
logs,
isFromRestore: false,
})
);
},
true
);
}
export async function cancelRefundSwap(swapId: string) {
try {
const swapInfo = await getSwapInfo(swapId);
const previousLogs = await getSavedLogsOfSwapId(swapId);
...
} catch (e) {
logger.error(`Error occurred: ${e.message}`);
}
}

src/main/main.ts Outdated
Comment on lines 127 to 139
.whenReady()
.then(async () => {
createWindow();
registerIpcHandlers();
initSocket();
watchDatabase();
spawnBalanceCheck();
watchElectrumTransactions();
watchLogs();
initStats();
startRPC();

setInterval(() => {
getRawSwapInfos();
}, 1000 * 20);

return 0;
})
.catch((e) =>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider using a more robust error handling strategy. Currently, if an error occurs during the execution of the 'whenReady' function, the error is simply logged and the execution continues. This might lead to unexpected behavior later in the execution. Consider throwing the error or handling it in a way that prevents further execution in case of a critical failure.

Suggested change
.whenReady()
.then(async () => {
createWindow();
registerIpcHandlers();
initSocket();
watchDatabase();
spawnBalanceCheck();
watchElectrumTransactions();
watchLogs();
initStats();
startRPC();
setInterval(() => {
getRawSwapInfos();
}, 1000 * 20);
return 0;
})
.catch((e) =>
try {
await app.whenReady();
createWindow();
initSocket();
startRPC();
setInterval(() => {
getRawSwapInfos();
}, 1000 * 20);
} catch (e) {
logger.error(
{ error: e.toString() },
'Failed to initialize application'
);
throw e;
}

src/main/main.ts Outdated
Comment on lines 198 to 238
export function sendSnackbarAlertToRenderer(
message: string,
variant: string,
autoHideDuration: number | null,
key: string | null
) {
function send() {
logger.debug(
{ message, variant, autoHideDuration, key },
'Attempting to send snackbar alert to renderer'
);
if (mainWindow) {
if (
mainWindow.webContents.isDestroyed() ||
mainWindow.webContents.isLoading()
) {
logger.debug(
'Main window is loading, waiting for it to finish before sending snackbar alert'
);
mainWindow.webContents.once('did-finish-load', () =>
setTimeout(send, 5000)
);
} else {
logger.debug(
{ message, variant, autoHideDuration, key },
'Sending snackbar alert to renderer'
);
mainWindow?.webContents.send(
'display-snackbar-alert',
message,
variant,
autoHideDuration,
key
);
}
} else {
setTimeout(send, 1000);
}
}
send();
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider using a more robust error handling strategy. Currently, if an error occurs during the execution of the 'sendSnackbarAlertToRenderer' function, the error is simply logged and the execution continues. This might lead to unexpected behavior later in the execution. Consider throwing the error or handling it in a way that prevents further execution in case of a critical failure.

Suggested change
export function sendSnackbarAlertToRenderer(
message: string,
variant: string,
autoHideDuration: number | null,
key: string | null
) {
function send() {
logger.debug(
{ message, variant, autoHideDuration, key },
'Attempting to send snackbar alert to renderer'
);
if (mainWindow) {
if (
mainWindow.webContents.isDestroyed() ||
mainWindow.webContents.isLoading()
) {
logger.debug(
'Main window is loading, waiting for it to finish before sending snackbar alert'
);
mainWindow.webContents.once('did-finish-load', () =>
setTimeout(send, 5000)
);
} else {
logger.debug(
{ message, variant, autoHideDuration, key },
'Sending snackbar alert to renderer'
);
mainWindow?.webContents.send(
'display-snackbar-alert',
message,
variant,
autoHideDuration,
key
);
}
} else {
setTimeout(send, 1000);
}
}
send();
}
export function sendSnackbarAlertToRenderer(
message: string,
variant: string,
autoHideDuration: number | null,
key: string | null
) {
function send() {
logger.debug(
{ message, variant, autoHideDuration, key },
'Attempting to send snackbar alert to renderer'
);
if (mainWindow) {
if (
mainWindow.webContents.isDestroyed() ||
mainWindow.webContents.isLoading()
) {
logger.debug(
'Main window is loading, waiting for it to finish before sending snackbar alert'
);
mainWindow.webContents.once('did-finish-load', () =>
setTimeout(send, 5000)
);
} else {
logger.debug(
{ message, variant, autoHideDuration, key },
'Sending snackbar alert to renderer'
);
try {
mainWindow?.webContents.send(
'display-snackbar-alert',
message,
variant,
autoHideDuration,
key
);
} catch (e) {
logger.error(
{ error: e.toString() },
'Failed to send snackbar alert to renderer'
);
throw e;
}
}
} else {
setTimeout(send, 1000);
}
}
send();
}

Comment on lines 40 to 43
export function getSwapTxFees(swap: GetSwapInfoResponse): number {
// TODO
return 124;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The function getSwapTxFees has a TODO comment and returns a hardcoded value. It would be better to implement the function or, if it's not possible at the moment, throw an error or return a more meaningful default value.

Suggested change
export function getSwapTxFees(swap: GetSwapInfoResponse): number {
// TODO
return 124;
}
export function getSwapTxFees(swap: GetSwapInfoResponse): number {
throw new Error('Function getSwapTxFees not implemented yet');
}

Comment on lines 53 to 58
export function getSwapExchangeRate(swap: GetSwapInfoResponse): number {
const btcAmount = getSwapBtcAmount(swap);
const xmrAmount = getSwapXmrAmount(swap);

return btcAmount / xmrAmount;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The function getSwapExchangeRate doesn't handle the case where xmrAmount is 0, which would cause a division by zero error. It would be better to add a check for this case.

Suggested change
export function getSwapExchangeRate(swap: GetSwapInfoResponse): number {
const btcAmount = getSwapBtcAmount(swap);
const xmrAmount = getSwapXmrAmount(swap);
return btcAmount / xmrAmount;
}
export function getSwapExchangeRate(swap: GetSwapInfoResponse): number {
const btcAmount = getSwapBtcAmount(swap);
const xmrAmount = getSwapXmrAmount(swap);
if (xmrAmount === 0) {
throw new Error('XMR amount is zero, cannot calculate exchange rate');
}
return btcAmount / xmrAmount;
}

Comment on lines +1 to +10
export enum RpcMethod {
GET_BTC_BALANCE = 'get_bitcoin_balance',
WITHDRAW_BTC = 'withdraw_btc',
BUY_XMR = 'buy_xmr',
RESUME_SWAP = 'resume_swap',
LIST_SELLERS = 'list_sellers',
CANCEL_REFUND_SWAP = 'cancel_refund_swap',
GET_SWAP_INFO = 'get_swap_info',
SUSPEND_CURRENT_SWAP = 'suspend_current_swap',
GET_HISTORY = 'get_history',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider using a more descriptive name for the enum RpcMethod. The current name is not very descriptive of what the enum represents. A more descriptive name could be RpcSwapMethods or RpcSwapActions.

Suggested change
export enum RpcMethod {
GET_BTC_BALANCE = 'get_bitcoin_balance',
WITHDRAW_BTC = 'withdraw_btc',
BUY_XMR = 'buy_xmr',
RESUME_SWAP = 'resume_swap',
LIST_SELLERS = 'list_sellers',
CANCEL_REFUND_SWAP = 'cancel_refund_swap',
GET_SWAP_INFO = 'get_swap_info',
SUSPEND_CURRENT_SWAP = 'suspend_current_swap',
GET_HISTORY = 'get_history',
export enum RpcSwapMethods {
GET_BTC_BALANCE = 'get_bitcoin_balance',
WITHDRAW_BTC = 'withdraw_btc',
BUY_XMR = 'buy_xmr',
RESUME_SWAP = 'resume_swap',
LIST_SELLERS = 'list_sellers',
CANCEL_REFUND_SWAP = 'cancel_refund_swap',
GET_SWAP_INFO = 'get_swap_info',
SUSPEND_CURRENT_SWAP = 'suspend_current_swap',
GET_HISTORY = 'get_history',
}

Comment on lines +15 to 18
const balance = useAppSelector((s) => s.rpc.state.balance);

if (balance == null || balance <= 0) {
return <></>;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Consider adding error handling for the case when balance is null. Currently, the code simply returns an empty fragment when balance is null or less than or equal to 0. It would be more user-friendly to display a message indicating that the balance could not be retrieved.

Suggested change
const balance = useAppSelector((s) => s.rpc.state.balance);
if (balance == null || balance <= 0) {
return <></>;
const balance = useAppSelector((s) => s.rpc.state.balance);
if (balance == null || balance <= 0) {
return <Alert severity="error">Could not retrieve balance</Alert>;
}

@binarybaron binarybaron force-pushed the cli-to-rpc branch 2 times, most recently from 57ddb9a to d7b56b3 Compare June 12, 2024 13:36
@binarybaron binarybaron merged commit 2a8e8fb into main Jun 12, 2024
0 of 5 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants